在项目中安装 Koa2
在项目中安装 Koa 和 MongoDB
首先在项目根目录下建立文件夹 service,并进入文件夹
使用 npm init -y 生成并初始化 package.js 文件
在命令行使用 npm 来安装 koa
npm install –save koa
编写一个 Hello World 测试安装是否成功
1 | // service 目录下新建 index.js |
编写好后,使用 node index.js 来启动服务,然后在浏览器中输入 http://localhost:3000,如果正常显示 hello world,说明 koa2 已经安装成功啦。
安装 MongoDB 数据库
安装步骤
- 在官网下载 MongoDB
- 下载之后进行安装,选择默认安装
- 安装时如果有安全软件报拦截一律允许就好啦,否则胡安装失败
- 安装完成后,需要配置 “环境变量”, 目的是在命令行中直接使用,而不用驶入很长的路径
运行 MongoDB 服务端
安装好 MongoDB 数据库之后,需要启用服务端才能使用。启用服务的命令是: Mongod
- 打开命令行: win + R ===> cmd
- 执行 mongod : 在命令中直接输入 mongod,会发现服务并没有启动,报了一个 exception,服务停止
- 新建文件夹:出现上面的错误,是因为还没有建立 MongoDB 需要的文件夹,一般是安装盘的根目录,建立 data/db,这两个文件夹
- 运行 mongod:这时候服务就可以开启了,链接默认端口是 27017
下载 Robo3
由于我们是做项目,所以图形界面比较直观,我们上边没有安装图形界面,这里使用 Robo3 来弥补一下
下载地址:Robo3
然后就是下一步下一步安装
Koa 用 Mongoose 连接数据库 (1)
Mongoose 概念
Mongoose 是一个开源的封装好的实现 Node 和 MongoDB 数据通讯的数据建模库
Mongoose 安装
npm install mongoose –save // 使用 npm 进行安装
连接数据库
在项目 service 文件夹下新建一个 database 文件夹,用来存放和数据库操作相关的文件。 在 database 文件夹下, 新建一个 init.js 文件,用来做数据库的连接和一些初始化的事情
1 | // /service/database/init.js |
然后在 /service/index.js 里加入立即执行函数,在使用之前要使用 require 进行引入 connect
1 | //引入connect |
然后在终端中运行 node index.js 命令,就可以看到数据库已经连接成功了
Koa 用 Mongoose 连接数据库 (2)
前面已经做了基本的数据库连接,并且已经连接成功。但是如果数据没有开启,或者网络出现问题,我们并没有做这些意外处理,在写程序时,当主要功能完成时,我们要作意外处理和逻辑处理,让程序增加健壮性
增加 Promise 支持
在做 init.js 文件时,必须确保先连接数据库后,在做其他事情,所以需要在所有代码的外层增加一个 Promise
1 | return new Promise ((resolve,reject)=>{ |
连接失败自动重连
一般数据库连接失败,我们会重新连接,但这个重连也是需要有一个次数的,比如连接 3 次失败,我们在服务端抛出异常
首先声明一个最大连接数 maxConnectTimes.
let maxConnectTimes = 0
当连接断开时,进行重连的代码如下
1 | mongoose.connection.on('disconnected',()=>{ |
当连接断开时,需要把连接次数 +1,并重连数据库。当重连次数超过 3 次后,我们抛出异常,并用 reject() 通知 Promise
同样当连接出错时,我们也要进行重新连接操作
1 | mongoose.connection.on('error',err=>{ |
全部文件
1 | const mongoose = require('mongoose') |
Mongoose 的 Schema 建模
数据库已经可以连接成功了,现在我们来了解一下如何建模,也就是定义 Schema,相当于 MongoDB 数据库的一个映射。 Schema 是一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力。 Schema 是以 key-value 形式 json 格式的数据。
Schema 中的数据类型
- String:字符串类型
- Number:数字类型
- Date:日期类型
- Boolean:布尔类型
- Buffer:NodeJS buffer 类型
- ObjectID:主键,一种特殊而非常重要的类型
- Mixed:混合类型
- Array:集合类型
Mongoose 中的三个概念
- schema:用来定义表的模板,实现和 MongoDB 数据库的映射。用来实现每个字段的类型,长度,映射的字段,不具备表的操作能力。
- model: 具备某张表操作能力的一个集合,是 mongoose 的核心能力。
- entity: 类似记录,由 Model 创建的实体,也具有数据库的操作能力。
初学定义一个用户 Schema
我们先以用户表为例,定义一个基本数据模型,( 当然并不完善,后面会逐步完善这个模型,并加入一些安全的机制进去 )
在 /service/database/ 文件夹下新建一个 schema 文件夹, 然后新建一个 User.js 文件。
1 | const mongoose = require('mongoose') //引入Mongoose |
载入 Schema 和插入查出数据
Schema 建立好以后,需要载入这些数据库,最好的方法就是在后台服务已启动的时候就把载入做好,所以在 service/init.js 里做这件事情,然后在 index.js 里直接执行
载入所有 Schema
在 service/init.js 引入一个 glob 和一个 resolve
首先安装 glob
npm install glob –save
const glob = require(‘glob’)
const { resolve } = require(‘path’)
- glob:node 的 glob 模块允许使用 * 等符号,来写一个 glob 规则,像在 shell 里一样,获取匹配对应规则文件
- resolve:将一系列路径或路径段解析为绝对路径
1 | // 了解两个引入的模块用法后,就可以一次性引入所有的 Schema 文件了。 |
插入一条数据
在操作数据库之前先引入我们的 Mongoose 和 刚写好的 initSchemas
1 | const mongoose = require('mongoose') |
引入好后,直接在 service/index.js 的立即执行函数里插入一条 User 数据
1 | ;(async () =>{ |
读出已经插入进去的数据
1 | let users = await User.findOne({}).exec() |
完整的 index.js 代码如下:
1 | const Koa = require('koa') |
打造安全的用户密码加密机制
加密处理
密码加密有很多种算法,比如 MD5 加密或者 hash256 加密算法;可以通过 哈希加密算法 这个网站直观地了解一下加密算法
加盐处理
有时候用户的密码设置的太过简单,很容易受到暴力破解或者用彩虹表破解。这时候就要使用加盐技术了,其实就是在原来的密码里,加入一些其他的字符串,并且我们可以自己设置加入字符串的强度。
把加盐的数据库密码进行 hash 处理后,在存入数据库就比较安全了
bcrypt 的使用
bcrypt 是一种跨平台的文件加密工具。有它加密的文件可在所有支持的操作系统和处理器上进行转译。它的口令必须是 8 至 56 个字符,并将在内部被转化为 448 位的密钥
使用 npm 进行安装 // 安装在 service 文件夹里面
npm install –save –registry=https://registry.npm.taobao.org
因为 bcrypt 里边的二进制包的下载可能被墙掉了,可以尝试使用淘宝源来进行安装
引入 bcrypt
const bcrypt = require( ‘bcrype’ ) // /service/database/schema/User.js 文件
用 pre 每次进行保存时都进行加盐加密的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* /service/database/schema/User.js 文件 */
//每次存储数据时都要执行
userSchema.pre('save', function(next){
// let user = this
// console.log(this)
bcrypt.genSalt( SALT_WORK_FACTOR,(err,salt)=>{
if(err) return next(err)
bcrypt.hash(this.password,salt, (err,hash)=>{
if(err) return next(err)
this.password = hash
next()
})
})
})
编写注册页面前端视图
注册页面的 vue 模板编写
新建页面
首先,来新建一个 vue 的模板文件: src/components/pages/Register.vue
编写 vue 的路由配置文件 router/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13import Vue from 'vue'
import Router from 'vue-router'
import ShoppingMall from '@/components/pages/ShoppingMall'
import Register from '@/components/pages/Register'
Vue.use(Router)
export default new Router({
routes: [
{path: '/',name: 'ShoppingMall',component: ShoppingMall},
{path: '/register',name: 'Register',component: Register},
]
})引入 Vant 的两个插件 Field 和 NavBar
1
2
3// main.js
import { Button, Row, Col, Search, Swipe, SwipeItem, Lazyload, List, Field, NavBar } from 'vant'
Vue.use(Button).use(Row).use(Col).use(Search).use(Swipe).use(SwipeItem).use(Lazyload).use(List).use(Field).use(NavBar)
编写模板文件,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61<template>
<div>
<van-nav-bar
title="用户注册"
left-text="返回"
left-arrow
@click-left="goBack"
/>
<div class="register-panel">
<van-field
v-model="username"
label="用户名"
icon="clear"
placeholder="请输入用户名"
required
@click-icon="username = ''"
/>
<van-field
v-model="password"
type="password"
label="密码"
placeholder="请输入密码"
required
/>
<div class="register-button">
<van-button type="primary" size="large">马上注册</van-button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
}
},
methods: {
goBack() {
this.$router.go(-1)
}
},
}
</script>
<style scoped>
.register-panel{
width:96%;
border-radius: 5px;
margin:20px auto;
padding-bottom:50px;
}
.register-button{
padding-top:10px;
}
</style>
Koa2 的用户操作的路由模块化
把所有的路由都写在 service/index.js 里是不正确的选择,这回导致我们的 index.js 页面越来越臃肿,最后变得没办法维护。我们需要把 Koa 程序模块化,也叫做路由模块化
安装 Koa-router
进入到 service 文件夹下,打开命令行,使用 npm 安装:
npm install koa-router –save // 这里下载的版本是 7.4.0
新建一个 User.js 文件
新建一个 appApi 文件夹,然后进入文件夹,新建 user.js 文件。有关 User.js 的操作,以后都会放到这个文件下,就是要编写的供前台使用的接口程序
编写 user.js 文件:
1 | const Router = require ( 'koa-router' ) |
让路由模块化
首先在 index.js 的文件顶部,引入 koa-router
const Router = require(‘koa-router’)
引入 user.js 模块
let user = require(‘./appApi/user.js’)
装载所有子路由
let router = new Router();
router.use(‘/user’,user.routes())
加载路由中间件
app.use(router.routes())
app.use(router.allowedMethods())
做完这四步,我们就可以在浏览器中试一下我们的模块化路由是否起作用了,在浏览器中输入 localhost:3000/user,已经可以出现我们设定好的页面了 ( 需要首先使用 node index.js 启动服务 )
总结: 通过这种简单的模块化路由机制,我们就实现了文件的分离,当然这并不是最完美的方案,如果对要求比较高的朋友,可以看一下 egg.js 的路由写法或者直接使用 egg.js 来进行开发。当然现在这种做法完全可以应付小型项目的开发
打通注册用户的前后端通讯
这一步的主要作用是 使用 API 接口的形式可以在前后端互相通讯和传递数据
安装 koa-bodyparser 中间件
首先要接到前端发过来的请求,这时候需要安装 koa-bodyparser 中间件,进入到 server 目录下,使用 npm 来进行安装
npm install –save koa-bodyparser // 这里安装的是 4.2.1 版本
安装好之后, 在 service/index.js 文件中注册和引入中间件
const bodyParser = require( ‘koa-bodyparser’ )
app.use(bodyParser());
前台的 axios 请求数据
在 register.vue 头部引入 axios
import axios from ‘axios’
修改 serviceAPI.config.js 接口配置文件
需要对接口配置文件做一些设置。加入我们的注册接口地址
1
2
3
4
5
6
7
8
9
10
11const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/"
const LOCALURL = "http://localhost:3000/"
const URL = {
getShoppingMallInfo:BASEURL+'index',
getGoodsInfo:BASEURL+'getGoodsInfo',
registerUser:LOCALURL+'user/register', //用户注册接口
}
module.exports = URL
// 这里主要加入了 LOCALURL 常量的声明,用于存储本地请求路径,和在 URL 里增加了 registerUser 接口的地址配置引入接口配置文件
import url from ‘@/serviceAPI.config.js’
编写 axios 用户注册方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 将下面的代码写在 src/components/pages/Register.vue 文件下的 methods 属性里面
//*********axios注册用户方法********
axiosRegisterUser(){
axios({
url: url.registerUser,
method: 'post',
data:{
username:this.username,
password:this.password
}
})
.then(response => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}给注册按钮绑定 axiosRegisterUser 方法
马上注册
让 koa2 支持跨域请求
安装 koa2-cors 中间件
在 koa2 里解决跨域的中间件叫 koa2-cors,先进入到 service 文件夹,使用 npm 来进行安装
npm install –save koa2-cors
在 service/index.js 文件中引入和注册 (使用) 中间件:
const cors = require( ‘koa2-cors’ )
app.use(cors())
编写 koa2 接收前台数据的方法
现在前台和后台数据互通的基本环节已经做好了,我们再写一个后台的数据接口方法,就可以实现接收数据,并回传数据了
进入 service/appApi/user.js 文件,修改 register 路由接口下的代码,记得把 get 方法换成 post 方法。
1 | router.post('/register',async(ctx)=>{ |
至此我们已经完成了前后台数据的互通,现在我们可以打开服务接口,开启浏览器测试一下
用户注册数据库操作
Koa2 的 User.js 接口的完善
首先在 service/appApi/user.js 下引入 mongose,这样就可以操作我们的 Schema 了。
const mongoose = require(‘mongoose’)
编写 register 接口的程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22router.post('/register',async(ctx)=>{
//取得Model
const User = mongoose.model('User')
//把从前端接收的POST数据封装成一个新的user对象
let newUser = new User(ctx.request.body)
//用mongoose的save方法直接存储,然后判断是否成功,返回相应的结果
await newUser.save().then(()=>{
//成功返回code=200,并返回成功信息
ctx.body={
code:200,
message:'注册成功'
}
}).catch(error=>{
//失败返回code=500,并返回错误信息
ctx.body={
code:500,
message:error
}
})
})
前端 vue 的业务处理
前面我们只是使用 axios 发送了一个请求,对返回的结果只是简单的 console.log 了一下,现在我们把这些代码进行补全,根据 koa 端返回的 code 进行不同的提示。
我们可以使用 Vant 的轻提示插件 Toast,先引入 Tosat 组件,直接在 Register.vue 下引入就可以了
import { Toast } from ‘vant’
具体业务逻辑代码如下:
1 | axiosRegisterUser(){ |
然后可以测试一下,看看是否可以插入数据库,并给用户一个有好的提示
注册的防重复提交
1. 在按钮上绑定 loading 属性
首先打开 src/components/pages/Register.vue 文件,找到注册按钮,然后在按钮上绑定 loadding 属性
马上注册
然后在线面的 JavaScript 部分的 data 中声明 openLoading 属性
1 | data() { |
2. 改写注册方法
防重复提交要在进入注册业务逻辑的时候就开始实行,也就是点击注册按钮这一步时。所以我们现在要改造已经写好的 axiosRegisterUser() 方法,加入一些防重复提交的逻辑
1 | axios({ |
在一开始进入注册方法的时候,做的第一件事就是把注册按钮变成 loading 状态。然后在注册失败的时候取消 loading 状态,注册成功就跳转到个人中心页面 (这部分还没做). 这样在前台就防止了重复提交
解决一个小问题
在程序中定义的是 user 而真实数据库中变成了 users,我们可以在 Schema 里配置一下解决这个问题
打开 service/database/schema/User.js 修改 new 之后的代码。 加入 { collection: ‘user’ }
1 | const userSchema = new Schema({ |
注册时的前端验证
1. 首先为 Field 绑定 error-message 属性
vant 框架提供的 field 属性提供了错误提示的机制,就是 error-message 属性。先在 script 的 data 里注册两个属性, usernameErrorMsg 和 passwordErrorMsg。 当值不符时,作用户提示使用。
1 | // 在 template 里面的相应位置添加上 :error-message = "usernameErrorMsg" 和 :error-message = "passwordErrorMsg" |
2. 编写验证方法 checkForm
在 methods 里增加一个 checkForm() 方法,用来验证表单信息,代码如下:
1 | checkForm(){ |
3. 重新编写注册方法
这时我们把一个注册分为了两个业务逻辑,第一步是检验表单数据,第二步向端口发送数据等待结果。如果直接在以前的 axiosRegisterUser() 方法上改造显得很不优雅,代码会很长,维护起来会增加额外的成本。新建一个 registerAction() 方法。
1 | registerAction(){ |
4. 重新绑定注册按钮事件
原来的按钮事件直接调用了 axiosRegisterUser() 方法,这时候我们要更换为新的 registerAction() 方法。
马上注册
Vue 的登陆界面制作和路由配置
1. 新建 Login.vue 文件
在 src/components/pages 文件夹下新建 Login.vue 文件。 因为登录页和注册页样式基本相同,所以可以直接复制注册页代码 然后进行修改
1 | <template> |
2. 配置路由,让页面可以正常访问
打开 /src/router/index.js 页面,配置路由,代码如下
1 | import Vue from 'vue' |
配置好之后启动服务,打开 http://localhost:8080/#/login 进行测试。
登陆的服务端业务逻辑代码
登陆业务的简单业务逻辑就是得到前端发来的用户名和密码,然后跟数据库进行比对,如果正确就显示登陆成功,失败就显示登陆失败
Schema中的对比实例方法
需要在 Schema 中制作一个比对的实例方法,这个方法就是比对我们加盐加密后的密码的。在 service/database/schema/User.js 中增加下面的代码:
1 | userSchema.methods = { |
编写登陆的 API 接口
进入 service/appApi/user.js,增加一个 login 路由, 并在路由内写入业务逻辑代码。
1 | /*登录的实践 */ |
前后端结合调试
在 src/serviceAPI.config.js 下加入接口代码。
1
2
3
4
5
6
7
8
9
10const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/"
const LOCALURL = "http://localhost:3000/"
const URL = {
getShoppingMallInfo:BASEURL+'index',
getGoodsInfo:BASEURL+'getGoodsInfo',
registerUser:LOCALURL+'user/register', //用户注册接口
login:LOCALURL+'user/login', //用户注册接口
}
module.exports = URL修改 src/components/pages/Login.vue 把 axios 的 Url 改成我们的 login 接口地址。
然后打开浏览器进行调试,就会得到成功与不成功。
登录的前端交互效果制作和登录状态存储
前端交互效果制作
目前为止从 Koa2 服务端是可以取到登录结果的,但是前端只是简单的打印了出来,并没有做任何交互。我们要在返回登录成功时,给用户一个 Toast 提示,并跳转到首页 ( 个人中心页面,暂未做 ),当返回登录失败的时候,要提示用户登录失败,并把登录按钮重新启用,可以再次登录
在 /src/components/pages/Login.vue 的 axiosLoginUser 方法里修改代码如下:
1 | axiosLoginUser(){ |
保存用户登录状态
移动端的应用有一个特殊的地方,就是当用户登录一次后,下次就不用登录了。这时候登录的信息是存储到了本地的 LocalStorage 里了。这个操作要等取到正确的登陆状态以后再执行,也就是要在 axios 返回了登录成功结果以后执行。代码如下:
1 | new Promise((resolve,reject) => { |
我们保存了用户登录状态以后,就有了一个是否登录的依据,然后我们就不会重复登录了,我们在已进入登录页面的 created 生命周期里,就判断是否已经登录。
1 | created(){ |
这时候如果已经登录后,再去登录页他会直接跳转到首页,并且提示你已经登录过了。
这步做完后 Login.vue 页面 script 部分完整代码如下:
1 | import axios from 'axios' |
商品详细数据的提纯操作
现在开始准备页面的商品数据和类别数据,目的是制作列表页和详情页。我们需要在 5W 多条商品详情的 JSON 数据中把有用的数据筛选出来,这种筛选叫做数据的提纯。
用 fs 读入数据
在 service 文件夹下,新建一个 fsJson.js 的文件使用 node 的 fs 模块,可以轻松把文件读取到程序中,然后进行遍历,把有用的数据提取出来,写入到一个新的数组中。代码如下:
1 | const fs = require('fs') |
写入到新的文件中
1 | fs.writeFile('./newGoods.json',JSON.stringify(pushData),function(err){ |
这样就完成了这次数据的提纯操作,通过提纯我们得到了一张可用的 json 商品详情表。
通过这节的学习主要是了解一下 node 的 fs 模块,这在工作中是非常常用的。
1 | // fsJson.js 文件完整代码: |
批量插入商品详情数据到 MongoDB 中
有了可插入的 JSON 数据,就可以建立一个 Schema 模型, 然后是用 Mongoose 插入到我们的数据库中。
1. 建立 Goods 的 Schema
建立 service/database/schema/Goods.js 文件,然后根据我们的数据表结构建立模型。代码如下:
1 | const mongoose = require('mongoose') // 引入 Mongoose |
2. 批量插入数据库的路由方法
新建一个 service/appApi/goods.js 以后关于商品的操作就都在这个 api 文件中编写了,我们也是要写路由的形式,提供每一个支持。
1 | const Koa = require('koa'); |
3. 把路由加入到 index.js 里
写好了 goods.js 的路由方法,就可以把它暴露到 index.js 里了
1 | // /service/index.js |
做完这一步,就可以在浏览器运行一下 http://localhost:3000/goods/insertAllGoodsInfo,运行结束后,可以到数据库看一下插入情况批量插入商品详情数据到 MongoDB 中
有了可插入的 JSON 数据,就可以建立一个 Schema 模型, 然后是用 Mongoose 插入到我们的数据库中。
1. 建立 Goods 的 Schema
建立 service/database/schema/Goods.js 文件,然后根据我们的数据表结构建立模型。代码如下:
1 | const mongoose = require('mongoose') // 引入 Mongoose |
2. 批量插入数据库的路由方法
新建一个 service/appApi/goods.js 以后关于商品的操作就都在这个 api 文件中编写了,我们也是要写路由的形式,提供每一个支持。
1 | const Koa = require('koa'); |
3. 把路由加入到 index.js 里
写好了 goods.js 的路由方法,就可以把它暴露到 index.js 里了
1 | // /service/index.js |
做完这一步,就可以在浏览器运行一下 http://localhost:3000/goods/insertAllGoodsInfo,运行结束后,可以到数据库看一下插入情况批量插入商品详情数据到 MongoDB 中
有了可插入的 JSON 数据,就可以建立一个 Schema 模型, 然后是用 Mongoose 插入到我们的数据库中。
1. 建立 Goods 的 Schema
建立 service/database/schema/Goods.js 文件,然后根据我们的数据表结构建立模型。代码如下:
1 | const mongoose = require('mongoose') // 引入 Mongoose |
2. 批量插入数据库的路由方法
新建一个 service/appApi/goods.js 以后关于商品的操作就都在这个 api 文件中编写了,我们也是要写路由的形式,提供每一个支持。
1 | const Koa = require('koa'); |
3. 把路由加入到 index.js 里
写好了 goods.js 的路由方法,就可以把它暴露到 index.js 里了
1 | // /service/index.js |
做完这一步,就可以在浏览器运行一下 http://localhost:3000/goods/insertAllGoodsInfo,运行结束后,可以到数据库看一下插入情况
商品大类的 Shema 建立和导入数据库
这节主要是把商品大类的数据从 JSON 格式导入到 MongoDB 数据库中,涉及到的知识点有 Schema 的建立, fs 模块的使用和 Mongoose 的存储。
1. 编写 Category 的 Schema
可以把 Schema 想象成数据库 collections 的一个复制映射,它是和数据库里的 collection 对应的。明白了这个,我们直接根据 JSON 的格式直接制作就好了
1 | // /service/database/schema/Category.js |
插入 MongoDB 数据库
有了 Schema 之后可以在 service/appApi/goods.js 文件里增加一个路由配置,并把业务逻辑代码写入到路由里。
- 用 fs 读取 category.json 的数据
- 把数据进行循环存入数据库
1 | router.get('/insertAllCategory',async(ctx)=>{ |
完成后可以打开 mongod 服务, 然后访问 http://localhost:3000/goods/insertAllCategory,数据就可以顺利插入到数据库离了。子数据的操作也是差不多一样的。
商品子类的 Shema 建立和导入数据库
1. categorySub 的 Schema 建立
1 | // service/database/schema/ |
2. 保存到数据库的业务逻辑
1 | // service/appApi/goods.js |
编写商品详情页的数据接口
1. 编写后台数据接口
在 service/appApi/goods.js 里,新编写一个路由业务逻辑,并用 findOne 的形式查找出一条商品数据。代码如下:
1 | /* 获取商品详细信息的接口 */ |
首先获得了从前端得到的参数 goodsId, 然后得到 Goods 模型, 用模型的 findOne 方法查找数据,查找出来进行返回。
2. 编写 Goods.vue 页面
由于现在还不能正常查看接口,所以我们需要一个页面,来调用接口。 新建 /src/components/pages/Goods.vue 文件。 代码如下:
1 | <template> |
3. 把组件加入到路由管理器中
有了模块之后,我们需要把模块加入到路由管理之中,这样才可以正常访问。
1 | import Vue from 'vue' |
现在可以直接打开 http://localhost:8080/#/Goods 路径,然后打开控制台,可以发现已经顺利的从后台取得数据了。
改写程序,让程序更优雅
1 | // 把上面的代码进行改写,改成只用 async/await 的方式 |
商品详情页路由的制作和参数的传递
修改 goodsInfoComponent.vue 文件
修改这个文件主要是让它具有跳转能力和传递参数的能力:
- 在这个组件里新加入一个 props,接受 goodsId
- 编写一个页面跳转的方法,这里起名为 goGoodsPage
- 绑定单击事件进行跳转 @click=’goGoodsPage’
具体代码如下:
1 | <template> |
接收路由传递的参数
接收参数可以使用 this.$route.query.goodsId,这样就可以得到由其他页面传递过来的参数了。
在 src/components/pages/Goods.vue 的 created 生命周期里修改如下:
1 | created(){ |
改写首页,传递 goodsId 参数
在 src/components/pages/ShoppingMall.vue 里,热卖商品的属性部分绑定 goodsId- :goodsID=’item.goodsId’ 代码如下:
1 | <!--Hot Area--> |
商品详情的页面模板编写1
1. 引入 Vant 框架中的 NavBar 组件
在项目的 /src/main.js 文件中用 import 引入 NavBar 组件
import { NavBar } from ‘vant’
Vue.use(NavNar)
2. 使用 NavBar 制作头部导航
打开 /src/components/pages/Goods.vue 文件,编写 template 部分代码,直接使用 van-nav-bar 组件
1 | <div> |
- title: 是显示的标题,这里就起名字叫做商品详情
- left-text: 是左侧显示的内容,治理显示 “返回” 两个字
- left-arrow:是否显示左侧箭头,默认值是 true,也就是显示
- @click-left:绑定左侧按钮时触发的事件方法,方法名字叫做 onClickLeft
编写 onClickLeft 方法,这个方法里边现在只要能回退到上一层路由就可以了,暂时不写其他逻辑,以后使用了页面缓存功能后,还要增加消除缓存的操作。
1 | onClickLeft(){ |
打开浏览器,测试一下是否点击返回按钮可以实现返回上一级路由
3. 编写商品图片部分
直接使用 html 中的 img 标签把头图显示出来
1 | <div class="topimage-div"> |
这里还没有 goodsInfo 注册数据,所以还需要在 js 部分的 data 中先进行注册
1 | data() { |
然后在 getInfo 方法里, 对 goodsInfo 进行赋值:this.goodsInfo = response.data.message
直接这样写不够严谨,应该在赋值前判断一下 code 的值和 message 不为空,代码如下:
1 | if(response.data.code == 200 && response.data.message ){ |
商品详情的页面模板编写2
修改商品详情页面的布局
1 | <template> |
vant 标签组件的使用
首先引入标签组件,在 main.js 里直接引入。 代码如下:
1
2
3
4
5
6import { Tab,Tabs } from 'vant'
Vue.use(Field)
.use(Tab)
.use(Tabs)
// 引入之后就可以在商品组件中直接使用了编写 template 代码
1
2
3
4
5
6
7
8
9<van-tabs >
<van-tab title="商品详情">
<div class="detail" v-html="goodsInfo.DETAIL">
</div>
</van-tab>
<van-tab title="评价">
正在制作中
</van-tab>
</van-tabs>
解决图片有空隙问题
因为每个图片后面是有空格的,而图片占了宽度的 100%,所以空格被单独挤出了一行。
解决方法: 把字体设置为 0,但是这样做如果以后有图文混排就会出现不显示字体的 BUG。所以最好的就决方案是后端插入的时候就取消掉空格。
1 | .detail { |
商品详情的页面模板编写3
加入价格的过滤器和底部的加入购物车和购买按钮
加入 Filter 过滤器来格式化价格
页面中的价格并没有进行格式化,需要一个 Filter 来进行格式化,在制作首页的时候,已经制作了一个 Filter,直接使用就可以进行格式化了。
引入 moneyFilter.js 文件
1
import { toMoney } from '@/filter/moneyFilter.js'
在 js 部分编写 filters 属性
1
2
3
4
5filters: {
moneyFilter( money ){
return toMoney( money )
}
},给价格加上 Filter
1
{{ goodsInfo.PRESENT_PRICE | moneyFilter }}
通过上面的三步,就可以实现价格的格式化了。
底部购买按钮和加入购物车按钮
在 template 的最底部,加入一个层,并进行基本的 flex 布局
1 | <div class="goods-bottom"> |
编写 CSS 样式:
1 | .goods-bottom{ |
分类页面的数据读取
读取大类别的 API 制作
首先获取大类信息,在 /service/appApi/goods.js 里增加一个新的路由 getCategoryList 代码如下:
1 | router.get('/getCategoryList',async(ctx) => { |
打开浏览器进行测试 http://localhost:3000/goods/getCategoryList
读取小类别的 API 制作
在 /service/appApi/goods.js 里增加一个新的路由 getCategorySubList 代码如下:
1 | router.get('/getCategorySubList',async(ctx) => { |
打开浏览器进行测试, http://localhost:3000/goods/getCategorySubList
根据商品类别获取商品列表
在 /service/appApi/goods.js 里新增加一个新的路由 getGoodsListByCategorySubID 代码如下:
1 | router.get('/getGoodsListByCategorySubID',async(ctx) => { |
打开浏览器进行测试 http://localhost:3000/goods/getGoodsListByCategorySubID
商品详细页的滑动切换和吸顶效果
Tab 页的滑动切换
通过 van-tabs 里的 swipeable 属性就可以开启滑动切换 tab 页的效果,代码如下:
1 | <van-tabs swipeable> |
吸顶效果的制作
通过 van-tabs 里的 sticky 属性可以开启吸顶效果,也叫粘性布局,当 Tab 滚动到顶部时会自动吸顶。
1 | <van-tabs swipeable sticky> |
商品列表页的布局1
建立页面和配置列表页路由
在 /src/components/pages 文件夹下新建一个 CategoryList.vue 页面。 然后利用 vbase 命令快速建立基本结构。
有了基本的页面后,到 /src/router/index.js 文件里, 添加 CategoryList.vue 页面的路由
1 | import Vue from 'vue' |
做完这步,我们的页面就可以正常访问到了,访问地址 http://localhost:8080/#/CategoryList
标题栏的布局
使用 Vant 提供好的 van-nav-bar 组件为页面添加一个标题
1 | <template> |
如果希望它固定在头部, 可以加入 fixed 属性。
大类的侧边栏的布局
使用 row-col 进行布局,代码如下:
1 | <div> |
axios 读取左侧大类
用 import 先导入 axios 和 serviceAPI.config.js,代码如下:
import axios from ‘axios’
import url from ‘@/serviceAPI.config.js’
配置 serviceAPI.config.js 文件
1
2
3
4
5
6
7
8
9
10
11
12
13const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/"
const LOCALURL = "http://localhost:3000/"
const URL = {
getShoppingMallInfo:BASEURL+'index',
getGoodsInfo:BASEURL+'getGoodsInfo',
registerUser:LOCALURL+'user/register', //用户注册接口
login:LOCALURL+'user/login', //用户注册接口
getDetailGoodsInfo:LOCALURL+'goods/getDetailGoodsInfo', //得到商品详细数据
getCategoryList:LOCALURL+'goods/getCategoryList', //得到大类信息
}
module.exports = URL编写 axios 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20getCategory() {
axios({
url:url.getCategoryList,
method:'get',
})
.then(response=>{
console.log(response)
if(response.data.code == 200 && response.data.message ){
}else{
Toast('服务器错误,数据取得失败')
}
})
.catch(error=>{
console.log(error)
})
}在生命周期里加入 getCategory 方法
1
2
3created(){
this.getCategory();
},
商品列表页的大类交互效果
把大类列表放到左侧导航上
在 data 属性里注册 category 变量为数组类型
1
2
3
4
5data() {
return {
category: [],
}
},在 getCategory() 方法里的 axios 的回调方法里为 category 赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17getCategory() {
axios({
url: url.getCategoryList,
method: 'get',
})
.then(response => {
console.log(response)
if (response.data.code == 200 && response.data.message ){
this.category = response.data.message
} else {
Toast('服务器错误,数据获取失败')
}
})
.catch(error => {
console.log(error);
})
}在 template 部分利用 li 标签把数据循环出来
1
2
3
4
5
6
7<div id="leftNav">
<ul>
<li v-for="(item,index) in category" :key="index">
{{item.MALL_CATEGORY_NAME}}
</li>
</ul>
</div>编写 CSS 样式
1
2
3
4
5
6
7
8
9
10#leftNav{
background-color: aliceblue;
}
#leftNav ul li {
line-height: 2rem;
border-bottom:1px solid #E4E7ED;
padding:3px;
font-size:0.8rem;
text-align: center;
}在生命周期里加入 js,让左侧适应页面高度
1
2
3
4mounted(){
let winHeight = document.documentElement.clientHeight;
document.getElementById("leftNav").style.height = winHeight - 46 + "px";
},
点击后的交互效果制作 — 反白操作
当点击每个大类的时候,我们希望 CSS 是有所变化的,证明我们已经点击过了,前端也叫做反白操作。
编写一个背景为白色的 CSS 样式
1
2
3.categoryActive{
background-color: #fff;
}在 data 里注册一个 categoryIndex 变量,用来控制那个导航变成白色,这里默认是 0,就是当打开页面时第一个类别是白色的
1
2
3
4
5
6data() {
return {
category:[],
categoryIndex:0,
}
},编写 clickCategory 方法,点击大类时调用这个方法,方法就是把点击的索引传递过去,然后付给刚才注册的 categoryIndex 属性
1
2
3
4//点击大类的方法
clickCategory(index){
this.categoryIndex=index
}在 template 里注册这些方法,并进行 class 动态绑定, 代码如下:
1
2
3<li @click="clickCategory(index)" :class="{categoryActive:categoryIndex==index}" v-for="(item,index) in category" :key="index">
{{item.MALL_CATEGORY_NAME}}
</li>
一二级分类的联动效果制作
当点击一级分类的时候,二级分类要根据你点击的一级分类进行变化。可以直接使用 Vant 的 Tab 组件来实现。
在 serviceAPI.config.js 中加入接口
直接把 service 中写的 API 方法,加入到前端接口配置文件中,
getCategorySubList:LOCALURL+'goods/getCategorySubList',
,全部代码如下:
1 | const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/" |
改写后端接口
以前写后端接口时,为了测试,所以使用的是 get 方法,但是为了传送数据的安全和方便,现在改为 post 方法,并接受前端传递过来的 categoryId 参数。 代码如下:
1 | // service/appApi/goods.js |
获取小类的方法
在 CategoryList 文件的 methods 属性里, 加入一个 getCategorySubByCategoryId 方法。 这里主要使用 axios 来获取后端的数据。
1 | //根据大类ID读取小类类别列表 |
改写 clickCategory 方法,加入一个 categoryId 参数,然后在方法里调用刚才写的 getCategorySubByCategoryId 方法,这样就可以实现二级联动效果了。
1 | //点击大类的方法 |
用 Vant 的 Tabs 组件实现联动
这里直接使用 Vant 提供的 Tabs 组件来实现联动, 代码如下:
1 | <div class="tabCategorySub"> |
现在可以在浏览器中看一下效果了,但是还是有些小 Bug,就是在进入页面的时候是没有二级分类的。这个只要在 getCategory 方法的回调函数里调用一下 getCategorySubByCategoryId 方法就可以了
this.getCategorySubByCategoryId( this.category[0].ID )
这里的 this.category[0].ID 是通过数组索引取得默认的第一个分类的 ID 值
商品列表上拉加载效果的实现
在列表页中都是需要两个基本的功能需求: 上拉加载和下拉刷新。可以使用 Vant 的 List 组件来实现上拉加载效果。
实现上拉加载效果
首先引入,在 src/main.js 中加入引入代码:
import { List } from ‘vant’
Vue.use( List )
在 data 里声明两个属性: loading 和 finished
1
2
3
4
5
6
7
8
9
10
11data(){
return {
category: [],
categoryIndex: 0,
active: 0,
categorySub: [],
list: [],
loading: false, // 上拉加载使用
finished: false, // 下拉加载是否没有数据了
}
},在 methods 属性中编写 onLoad() 方法,用于实现上拉加载
1
2
3
4
5
6
7
8
9
10
11onLoad(){
setTimeout(()=>{
for(let i = 0; i < 10; i++) {
this.list.push(this.list.length+1);
}
this.loading = false;
if ( this.list.length >= 40 ) {
this.finished = true;
}
},500);
}在模板中加入组件代码, 代码如下
1
2
3
4
5
6
7
8
9
10
11<div id="list-div">
<van-list
v-model="loading"
:finished="finished"
@load="onLoad"
>
<div class="list-item" v-for="item in list" :key="item">
{{item}}
</div>
</van-list>
</div>预览效果,根据效果编写 CSS 样式。
1
2
3
4
5
6
7
8
9.list-item{
text-align: center;
line-height: 80px;
border-bottom: 1px solid #f0f0f0;
background-color: #fff;
}
#list-div{
overflow: scroll;
}在 mounted() 生命周期里设置 list-dev 的高度
1
2
3
4
5mounted(){
let winHeight = document.documentElement.clientHeight;
document.getElementById('leftNav').style.height = winHeight - 46 + 'px';
document.getElementById('list-div').style.height = winHeight - 90 + 'px';
},
商品列表页下拉刷新效果的实现
列表页的下拉刷新, 并不是指刷新整个页面。
引入 Vant 中的 PullRefresh 组件
引入 pullRefresh 组件就可以实现下拉刷新效果,先在 src/main.js 中引入一下。
import { PullRefresh } from ‘vant’
Vue.use(PullRefresh)
增加下拉刷新用的变量和方法
在 data 中增加一个 isRefresh 属性,用来说明现在的状态是否是下拉加载状态。
1
2
3
4
5
6
7
8
9
10
11
12data(){
return {
category: [],
categoryIndex: 0,
active: 0,
categorySub: [],
list: [],
loading: false, // 上拉加载使用
finished: false, // 上拉加载是否没有了
isRefresh: false, // 下拉加载
}
}然后写一个 onRefresh 方法,用来重新加载数据
1
2
3
4
5
6
7onRefresh(){
setTimeout( ()=>{
this.isRefresh = false;
this.list = [];
this.onLoad();
}, 500);
}先把 list 数组清空,然后再次调用 onLoad() 方法,这样就会刷新商品列表页面了
编写 template 部分
直接在 van-list 组件外边加入 van-pull-refresh 组件就可以实现了,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13<div id="list-div">
<van-pull-refresh v-model="isRefresh" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
@load="onLoad"
>
<div class="list-item" v-for="item in list" :key="item">
{{item}}
</div>
</van-list>
</van-pull-refresh>
</div>
商品类别分类的 Koa2 分页服务制作
修改 Koa2 服务代码
进入 service/appApi/goods.js 找到 getGoodsListByCategorySubID 这个请求进行改造
1 | router.post('/getGoodsListByCategorySubID',async(ctx)=>{ |
首先把请求方式改为了 post,然后从前台接收两个数据 categorySubId ( 子类ID号 ) 和 page ( 当前页数 )。定义每页显示的数量,然后再根据每页数量和当前页数算出开始读取的位置 start 。有了开始位置和每页显示数量,就尅顺利得出我们想要的结果了。
把写好的接口加入到前台
打开 src/serviceAPI.config.js 文件,在最后加入代码:
getGoodsListByCategorySubID:LOCALURL+’goods/getGoodsListByCategorySubID’, //得到小类商品信息
此时全部代码如下:
1 | const BASEURL = "https://www.easy-mock.com/mock/5ae2eeb23fbbf24d8cd7f0b6/SmileVue/" |
真实数据的上拉加载效果制作
在 data 属性里注册必要的参数
在 src/components/pages/CategoryList.vue 里注册几个属性,代码如下:
1 | data () { |
编写 axios 商品列表的获取方法
增加 getGoodList 方法,这个方法里传递两个参数,第一个是商品的子分类,第二个是请求分类的页数。
1 | getGoodList(){ |
点击获取子类商品信息的方法
编写点击子类 Tab,就可以获取子类 ID 的方法,在获取子类 ID 的同时,需要做一些变量的初始化操作,比如把 goodsList 变量清空。
1 | // 点击子类获取商品信息 |
改造大类的方法
在点击大类时,同样也要进行一些初始化操作
1 | // 点击大类的方法 |
编写 OnLoad 方法,实现上拉加载效果
1 | onLoad(){ |
编写 html 和 CSS
1 | // HTML 部分 |
真实数据的下拉刷新效果制作
下拉刷新效果的制作
在 /src/components/pages/CategoryList.vue 文件中改写 onRefresh() 方法
1 | onRefresh(){ |
解决首页类别导航图片大小不一的 BUG
先给 导航栏的层 添加一个 type-item 的 class
1
2
3
4
5
6
7// ShoppingMall.vue
<div class="type-bar">
<div class="type-item" v-for="(cate,index) in category" :key="index" >
<img v-lazy="cate.image" width="90%" />
<span>{{cate.mallCategoryName}}</span>
</div>
</div>然后在 CSS 部分加入一句 CSS 代码
1
2
3.type-item {
flex: 1;
}或者不用修改 html 部分,直接只添加一行 CSS 代码也可以
1
2
3.type-bar div {
flex: 1;
}
Vue 中图片失效替补图片的制作方法
在获取的商品信息中,有一些商品图片已经失效,如果在真实的项目中遇到这类问题,是不允许直接显示图片失效的,所以我们需要显示一个替补图片
制作一张替补图片
使用 PS 制作一张替补图片,尽量不要使用彩色,而是使用黑白灰来制作。或者直接在网上随便找一张。
把图片放到相应位置
把图片命名为 errorimg.png, 并放到 /src/assets/images/errorimg.png.
然后在 src/components/pages/CategoryList.vue 中的 data 中加入 errorImg 属性,代码如下。
1 | data() { |
在图片位置加入 onerror 事件
1 | <div class="list-item-img"> |
商品列表页编程式导航的制作
编程式导航
编写一个 goGoodsInfo() 方法,接收参数为 id,这里使用了 name 的导航方式
1
2
3goGoodsInfo (id) {
this.$router.push({name: 'Goods', params: {goodsId: id}})
}给 html 加入 click 事件
1
2
3
4
5
6
7
8
9<div class="list-item" @click="goGoodsInfo(item.ID)" v-for="(item,index) in goodList" :key="index" >
<div class="list-item-img">
<img :src="item.IMAGE1" width="100%" :onerror="errorImg"/>
</div>
<div class="list-item-text">
<div>{{item.NAME}}</div>
<div class="">¥{{item.ORI_PRICE}}</div>
</div>
</div>Goods.vue 页面的参数接收改造
以前使用的是 query 的方法接收,现在改用 params 的方法传递,所以要用到三元运算符做一下兼容处理
1
this.goodsId = this.$route.query.goodsId ? this.$route.query.goodsId : this.$route.params.goodsId
params 传参,路径不能使用 path,只能使用 name,不然取不到传的参数。
1
this.$router.push({name: 'Goods', params: {goodsId: id}})
取数据时用 params 获取
1
this.$route.params.goodsId
query 传参,用的是 path,而不是 name,否则也会出错
1
this.$router.push({path: '/Goods', query: {goodsId: id}})
取数据使用 query
1
this.$route.query.goodsId
价格过滤器的添加
首先引入过滤器
1
import {toMoney} from '@/filter/moneyFilter.js'
filters 属性的编写
1
2
3
4
5filters:{
moneyFilter(money){
return toMoney(money)
}
},在 template 中使用
1
<div class="">¥ {{item.ORI_PRICE | moneyFilter}}</div>
购物车页面的建立
购物车页面不和后台交互,并且还要保持用户数据的持久化,主要知识点在 H5 新增的 localStorage 本地存储里
建立购物车页面
在 /src/components/pages 目录下新建一个 Cart.vue 文件,然后建立组件的基本结构,代码如下。
1 | <template> |
加入头部部分
1 | <div class="navbar-div"> |
配置路由
打开 src/router/index.js 文件,增加 Cart 页面的路由配置
1 | import Vue from 'vue' |
配置完路由,可以访问 http://localhost:8080/#/Cart 看看是不是可以正常访问
得到购物车数据方法的编写
进入页面要做的第一件事就是取得 localStorage 里的数据,首先在 data 里注册两个属性 cartInfo (购物车中商品的信息) 和 isEmpty (购物车是否为空的标识,方便页面呈现),然后编写具体的 getCartInfo() 方法。代码如下
1 | export default { |
购物车中商品的添加
购物车商品的添加,其实就是对 localStorage 的操作和数组查找的使用,也就是 array.find() 操作。购物车添加的主要逻辑,并不放在 Cart.vue 页面里,我们放在 Goods.vue 页面里,也就是商品详情页面,这样做的好处是以后好扩展,并且不用传递参数,直接操作 localStorage
向购物车中添加商品
在 /components/pages/Goods.vue 里加入一个 addGoodsToCart 方法,然后编写下面的代码
1 | addGoodsToCart(){ |
完成上面代码编写后,向 “加入购物车” 按钮绑定 addGoodsToCart 事件方法
1 | <van-button size="large" type="primary" @click="addGoodsToCart">加入购物车</van-button> |
购物车清空和商品布局(Flex)
现在购物车里已经可以添加商品数据了,现在来将这些数据显示出来
清空购物车按钮的制作
由于以前存在了一些不合法的数据,现在需要把 localStorage 里的 cartInfo 数据清空,这时候需要一个清空按钮
/src/components/pages/Cart.vue 里的模板里写一个 button
1 | // 清空购物车 |
购物车商品的布局
使用 Flex 进行购物车商品的布局
1 | // 在 main.js 文件中导入 Stepper 组件 |
购物车中的商品价格计算
Vue 的双向数据绑定让计算价格变得相当容易,只要对 data 属性里的 cartInfo 属性进行编辑,就会自动呈现在页面上。
商品价格的格式化
引入过滤器,然后直接使用就可以了
引入 moneyFilter 过滤器
1
import {toMoney} from '@/filter/moneyFilter.js'
编写过滤器
1
2
3
4
5filters: {
moneyFilter(money){
return toMoney(money);
}
},使用过滤器
1
¥{{item.price | moneyFilter}}
改造 template,增加单个商品总价计算
直接写出数量和每个商品的总价,这里并不需要再 js 里写代码,直接在模板里使用乘法即可完成需求
1 | <div class="pang-goods-price"> |
商品总价的计算
商品总价使用 Vue 的计算属性就可以搞定
1 | // 首先编写模板,让商品总价显示出来 |
先声明了一个总价格 allMoney,然后用循环读出 cartInfo 里的数据,并给 allMoney 进行赋值,完成后重新写入 localStorage 里,最后返回 allMoney,就完成了计算属性的编写
底部导航栏和子导航的制作
现在已经有了三个页面,我们切换页面还是需要在地址栏输入,然后才能有所变化。现在我们来使用底部导航来切换页面
引入 tabbar 组件
在 /src/main.js 文件里引入两个 Vant 组件 Tabbar 和 Tabbarltem
1 | import {Tabbar, TabbarItem} from 'vant' |
新建 Main.vue 文件
在 /src/components/pages 下新建一个 Main.vue 文件,然后编写底部导航代码,代码如下
1 | <template> |
子导航的制作
修改 /src/router/index.js 文件,把 Main.vue 的导航加入进来,然后把商城首页、商品列表页和购物车页面变成他的子导航
1 | import Vue from 'vue' |
底部导航栏优化
现在已经有了底部菜单,但是还是有一些小 Bug 需要处理一下。比如说从商品详情页面进入购物车时底部导航不跟随变化,还有有些页面拉不到底部。
购物车页面底部导航的处理
购物车页面出现底部导航不跟随变化的主要问题是:我们不是点击导航进入的,而是通过编程式导航进入的,这样导航栏就没有互动和触发事件,所以并没有发生变化。解决的思路就是跟踪获取路径,根据路径改变导航栏的变化。
先写一个方法获得路径,然后判断路径是不是 /Cart,是就把 this.active 变成 2 就可以了。代码如下
1
2
3
4
5
6changeIabBarActive(){
this.nowPath = this.$router.path;
if(this.nowPath == '/Cart') {
this.active = 2;
}
}在进入页面的生命周期里引入这个方法
1
2
3created(){
this.changeTabBarActive()
}这时候这个 Bug 就解决了,如果页面中有多种这样的形式,可以逐一进行判断
商城首页不能来到底部的修改
产生这个问题的主要原因是在热销商品的层里没有加入高度,我们在这里个 .hot-goods 加入一段 CSS 样式就可以解决了
1 | .hot-goods{ |
列表页 Bug 的解决
列表页也有拉不到底的问题,因为列表页我们是动态计算出来的高度,所以我们直接在动态计算的地方减去 50px 就可以了
1 | mounted(){ |